package io.gitee.horizon007.webspider;

import io.gitee.horizon007.webspider.annotation.Context;
import io.gitee.horizon007.webspider.metadata.ClassMetadata;
import io.gitee.horizon007.webspider.metadata.MetadataUtils;
import io.gitee.horizon007.webspider.metadata.PropertyMetadata;
import io.gitee.horizon007.webspider.provider.DocumentProvider;
import io.gitee.horizon007.webspider.transformer.ValueTransformer;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class WebSpider implements AutoCloseable {

    private Document document;

    /** 线程池, 内部是以多线程的方式执行的, 如果没有设置, 在init时创建一个固定大小为10的线程池 */
    @Setter
    @Getter
    private ExecutorService executorService;

    /** parse方法内刚开始解析时是否启用异步执行(这不影响DEEP转换器的异步执行), 默认为true */
    @Getter
    @Setter
    private boolean parseAsyncEnabled = true;

    /**
     * 初始化内部数据
     *
     * @param provider 数据提供者
     * @throws Exception Exception
     */
    public void init(DocumentProvider provider) throws Exception {
        autoCreateExecutorService();

        if (provider.isAsync()) {
            Future<Document> future = executorService.submit(provider::getDocument);
            this.document = future.get();
        } else {
            this.document = provider.getDocument();
        }
    }

    /**
     * 解析上下文
     *
     * @param selector css选择器, 为查询到的所有元素都创建一个elementType
     * @param elementType 集合元素类型
     * @return List
     * @param <E> elementType
     * @throws Throwable Throwable
     */
    @SuppressWarnings("unchecked")
    public <E> List<E> parse(String selector, Class<E> elementType) throws Throwable {
        Objects.requireNonNull(document, "Document cannot be found, please initialize it first.");
        Element body = document.body();
        Elements elements = body.select(selector);

        if (CollectionUtils.isEmpty(elements)) {
            return Collections.emptyList();
        }

        final ClassMetadata classMetadata = MetadataUtils.metadata(elementType);

        // 单个元素时，就没必要异步执行了
        if (elements.size() == 1 || !parseAsyncEnabled) {
            return (List<E>) doFill(elements, classMetadata);
        }

        List<Future<List<Object>>> futures = new ArrayList<>(elements.size());

        for (Element element : elements) {
            Future<List<Object>> future = executorService.submit(() -> {
                try {
                    return doFill(new Elements(element), classMetadata);
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            });
            futures.add(future);
        }

        List<Object> result = new ArrayList<>(futures.size());

        for (int i = 0; i < futures.size(); i++) {
            List<Object> objects = futures.get(i).get();
            if (CollectionUtils.isNotEmpty(objects)) {
                result.add(objects.get(0));
            }
        }

        return (List<E>) result;
    }

    public List<Object> doFill(Elements context, ClassMetadata classMetadata) throws Throwable {
        MethodHandle noArgsConstructor = Objects.requireNonNull(classMetadata.getNoArgsConstructor(), String.format(
                "Class '%s' has no parameterless constructor.",
                classMetadata.getType().getName()
        ));

        List<PropertyMetadata> propertyMetadataList
                = getPropertyMetadataByAnnotation(classMetadata.getPropertyMetadataList(), Context.class);


        List<Object> result = new ArrayList<>();
        List<Future<?>> futures = null;

        if (!context.isEmpty()) {
            for (final Element element : context) {

                final Object instance = noArgsConstructor.invoke();

                for (PropertyMetadata propertyMetadata : propertyMetadataList) {
                    final Context annotation = propertyMetadata.getAnnotation(Context.class);

                    final ValueTransformer valueTransformer = annotation.transformer().getValueTransformer();

                    if (valueTransformer.isAsync()) {
                        if (futures == null) {
                            futures = new ArrayList<>(5);
                        }
                        // 这里内部的执行逻辑与else中一样，只不过考虑性能就不全都创建Callable了
                        futures.add(executorService.submit(() -> {
                            try {
                                Object value = valueTransformer.transform(
                                        this,
                                        determinePropertyContext(element, annotation),
                                        instance,
                                        propertyMetadata,
                                        annotation
                                );
                                setProperty(instance, propertyMetadata, value);
                            } catch (Throwable e) {
                                throw new RuntimeException(e);
                            }
                        }));
                    } else {
                        Object value = valueTransformer.transform(
                                this,
                                determinePropertyContext(element, annotation),
                                instance,
                                propertyMetadata,
                                annotation
                        );
                        setProperty(instance, propertyMetadata, value);
                    }
                }
                result.add(instance);
            }
        }

        if (CollectionUtils.isNotEmpty(futures)) {
            for (int i = 0; i < futures.size(); i++) {
                futures.get(i).get();
            }
        }

        return result;
    }


    /**
     * 未显式设置线程池时自动创建
     */
    private void autoCreateExecutorService() {
        if (executorService == null) {
            executorService = Executors.newFixedThreadPool(10);
        }
    }

    /**
     * 自动关闭线程池, 在close中调用
     */
    private void autoCloseExecutorService() {
        if (executorService != null) {
            executorService.shutdown();
            executorService = null;
        }
    }

    /**
     * 根据注解查找PropertyMetadata
     *
     * @param propertyMetadataList propertyMetadataList
     * @param annotationType annotationType
     * @return propertyMetadataList
     */
    private List<PropertyMetadata> getPropertyMetadataByAnnotation(
            List<PropertyMetadata> propertyMetadataList,
            final Class<? extends Annotation> annotationType
    ) {
        return propertyMetadataList.stream().filter(v -> v.getAnnotation(annotationType) != null).toList();
    }

    /**
     * 决定属性的上下文
     *
     * @param parentContext 属性所在对象的上下文
     * @param annotation 属性所标记的@Context注解
     * @return 上下文
     */
    private Elements determinePropertyContext(Element parentContext, Context annotation) {
        return StringUtils.isEmpty(annotation.selector()) ? new Elements(parentContext) : parentContext.select(annotation.selector());
    }

    /**
     * 设置属性值
     *
     * @param instance 对象实例
     * @param propertyMetadata 属性元数据
     * @param value 属性值
     * @throws Throwable Throwable
     */
    private void setProperty(Object instance, PropertyMetadata propertyMetadata, Object value) throws Throwable {
        propertyMetadata.getWriteMethod().invoke(instance, value);
    }

    @Override
    public void close() throws Exception {
        autoCloseExecutorService();
    }
}
